added Feb 2001 SDK
[windows-sources.git] / shared source / sscli20 / jscript / engine / jsobject.cs
blob4753fa9405fbf92e778c417acad1b7fca6379c7e
1 // ==++==
2 //
3 //
4 // Copyright (c) 2006 Microsoft Corporation. All rights reserved.
5 //
6 // The use and distribution terms for this software are contained in the file
7 // named license.txt, which can be found in the root of this distribution.
8 // By using this software in any fashion, you are agreeing to be bound by the
9 // terms of this license.
10 //
11 // You must not remove this notice, or any other, from this software.
12 //
13 //
14 // ==--==
16 namespace Microsoft.JScript {
18 using System;
19 using System.Collections;
20 using System.Diagnostics;
21 using System.Globalization;
22 using System.Reflection;
23 using System.Runtime.InteropServices.Expando;
25 public class JSObject : ScriptObject, IEnumerable, IExpando{
26 private bool isASubClass;
27 private IReflect subClassIR;
28 private SimpleHashtable memberCache;
29 internal bool noExpando;
30 internal SimpleHashtable name_table;
31 protected ArrayList field_table;
32 internal JSObject outer_class_instance;
34 public JSObject()
35 : this(null, false){
36 this.noExpando = false;
39 internal JSObject(ScriptObject parent)
40 : this(parent, true) {
43 internal JSObject(ScriptObject parent, bool checkSubType)
44 : base(parent) {
45 this.memberCache = null;
46 this.isASubClass = false;
47 this.subClassIR = null;
48 if (checkSubType){
49 Type subType = Globals.TypeRefs.ToReferenceContext(this.GetType());
50 Debug.Assert(subType != Typeob.BuiltinFunction);
51 if (subType != Typeob.JSObject){
52 this.isASubClass = true;
53 this.subClassIR = TypeReflector.GetTypeReflectorFor(subType);
55 }else
56 Debug.Assert(Globals.TypeRefs.ToReferenceContext(this.GetType()) == Typeob.JSObject);
57 this.noExpando = this.isASubClass;
58 this.name_table = null;
59 this.field_table = null;
60 this.outer_class_instance = null;
63 internal JSObject(ScriptObject parent, Type subType)
64 : base(parent) {
65 this.memberCache = null;
66 this.isASubClass = false;
67 this.subClassIR = null;
68 Debug.Assert(subType == this.GetType() || this.GetType() == typeof(BuiltinFunction));
70 subType = Globals.TypeRefs.ToReferenceContext(subType);
71 if (subType != Typeob.JSObject){
72 this.isASubClass = true;
73 this.subClassIR = TypeReflector.GetTypeReflectorFor(subType);
75 this.noExpando = this.isASubClass;
76 this.name_table = null;
77 this.field_table = null;
80 public FieldInfo AddField(String name){
81 if (this.noExpando)
82 return null;
83 FieldInfo field = (FieldInfo)this.NameTable[name];
84 if (field == null){
85 field = new JSExpandoField(name);
86 this.name_table[name] = field;
87 this.field_table.Add(field);
89 return field;
92 MethodInfo IExpando.AddMethod(String name, Delegate method){
93 return null;
96 PropertyInfo IExpando.AddProperty(String name){
97 return null;
100 internal override bool DeleteMember(String name){
101 FieldInfo field = (FieldInfo)this.NameTable[name];
102 if (field != null){
103 if (field is JSExpandoField){
104 field.SetValue(this, Missing.Value);
105 this.name_table.Remove(name);
106 this.field_table.Remove(field);
107 return true;
108 }else if (field is JSPrototypeField){
109 field.SetValue(this, Missing.Value);
110 return true;
111 }else
112 return false;
113 }else if (this.parent != null)
114 return LateBinding.DeleteMember(this.parent, name);
115 else
116 return false;
119 internal virtual String GetClassName(){
120 return "Object";
123 #if !DEBUG
124 [DebuggerStepThroughAttribute]
125 [DebuggerHiddenAttribute]
126 #endif
127 internal override Object GetDefaultValue(PreferredType preferred_type){
128 if (preferred_type == PreferredType.String){
129 ScriptFunction toString = this.GetMemberValue("toString") as ScriptFunction;
130 if (toString != null){
131 Object result = toString.Call(new Object[0], this);
132 if (result == null) return result;
133 IConvertible ic = Convert.GetIConvertible(result);
134 if (ic != null && ic.GetTypeCode() != TypeCode.Object) return result;
136 ScriptFunction valueOf = this.GetMemberValue("valueOf") as ScriptFunction;
137 if (valueOf != null){
138 Object result = valueOf.Call(new Object[0], this);
139 if (result == null) return result;
140 IConvertible ic = Convert.GetIConvertible(result);
141 if (ic != null && ic.GetTypeCode() != TypeCode.Object) return result;
143 }else if (preferred_type == PreferredType.LocaleString){
144 ScriptFunction toLocaleString = this.GetMemberValue("toLocaleString") as ScriptFunction;
145 if (toLocaleString != null){
146 return toLocaleString.Call(new Object[0], this);
148 }else{
149 if (preferred_type == PreferredType.Either && this is DateObject)
150 return this.GetDefaultValue(PreferredType.String);
151 ScriptFunction valueOf = this.GetMemberValue("valueOf") as ScriptFunction;
152 if (valueOf != null){
153 Object result = valueOf.Call(new Object[0], this);
154 if (result == null) return result;
155 IConvertible ic = Convert.GetIConvertible(result);
156 if (ic != null && ic.GetTypeCode() != TypeCode.Object) return result;
158 ScriptFunction toString = this.GetMemberValue("toString") as ScriptFunction;
159 if (toString != null){
160 Object result = toString.Call(new Object[0], this);
161 if (result == null) return result;
162 IConvertible ic = Convert.GetIConvertible(result);
163 if (ic != null && ic.GetTypeCode() != TypeCode.Object) return result;
166 return this;
169 IEnumerator IEnumerable.GetEnumerator(){
170 return ForIn.JScriptGetEnumerator(this);
173 private static bool IsHiddenMember(MemberInfo mem) {
174 // Members that are declared in super classes of JSObject are hidden except for those
175 // in Object.
176 Type mtype = mem.DeclaringType;
177 if (mtype == Typeob.JSObject || mtype == Typeob.ScriptObject ||
178 (mtype == Typeob.ArrayWrapper && mem.Name != "length"))
179 return true;
180 return false;
183 private MemberInfo[] GetLocalMember(String name, BindingFlags bindingAttr, bool wrapMembers){
184 MemberInfo[] result = null;
185 FieldInfo field = this.name_table == null ? null : (FieldInfo)this.name_table[name];
186 if (field == null && this.isASubClass){
187 if (this.memberCache != null){
188 result = (MemberInfo[])this.memberCache[name];
189 if (result != null) return result;
191 bindingAttr &= ~BindingFlags.NonPublic; //Never expose non public fields of old style objects
192 result = this.subClassIR.GetMember(name, bindingAttr);
193 if (result.Length == 0)
194 result = this.subClassIR.GetMember(name, (bindingAttr&~BindingFlags.Instance)|BindingFlags.Static);
195 int n = result.Length;
196 if (n > 0){
197 //Suppress any members that are declared in JSObject or earlier. But keep the ones in Object.
198 int hiddenMembers = 0;
199 foreach (MemberInfo mem in result){
200 if (JSObject.IsHiddenMember(mem))
201 hiddenMembers++;
203 if (hiddenMembers > 0 && !(n == 1 && this is ObjectPrototype && name == "ToString")){
204 MemberInfo[] newResult = new MemberInfo[n-hiddenMembers];
205 int j = 0;
206 foreach (MemberInfo mem in result){
207 if (!JSObject.IsHiddenMember(mem))
208 newResult[j++] = mem;
210 result = newResult;
213 if ((result == null || result.Length == 0) && (bindingAttr & BindingFlags.Public) != 0 && (bindingAttr & BindingFlags.Instance) != 0){
214 BindingFlags flags = (bindingAttr & BindingFlags.IgnoreCase) | BindingFlags.Public | BindingFlags.Instance;
215 if (this is StringObject)
216 result = TypeReflector.GetTypeReflectorFor(Typeob.String).GetMember(name, flags);
217 else if (this is NumberObject)
218 result = TypeReflector.GetTypeReflectorFor(((NumberObject)this).baseType).GetMember(name, flags);
219 else if (this is BooleanObject)
220 result = TypeReflector.GetTypeReflectorFor(Typeob.Boolean).GetMember(name, flags);
221 else if (this is StringConstructor)
222 result = TypeReflector.GetTypeReflectorFor(Typeob.String).GetMember(name, (flags|BindingFlags.Static)&~BindingFlags.Instance);
223 else if (this is BooleanConstructor)
224 result = TypeReflector.GetTypeReflectorFor(Typeob.Boolean).GetMember(name, (flags|BindingFlags.Static)&~BindingFlags.Instance);
225 else if (this is ArrayWrapper)
226 result = TypeReflector.GetTypeReflectorFor(Typeob.Array).GetMember(name, flags);
228 if (result != null && result.Length > 0){
229 if (wrapMembers)
230 result = ScriptObject.WrapMembers(result, this);
231 if (this.memberCache == null) this.memberCache = new SimpleHashtable(32);
232 this.memberCache[name] = result;
233 return result;
236 if ((bindingAttr&BindingFlags.IgnoreCase) != 0 && (result == null || result.Length == 0)){
237 result = null;
238 IDictionaryEnumerator e = this.name_table.GetEnumerator();
239 while (e.MoveNext()){
240 if (String.Compare(e.Key.ToString(), name, StringComparison.OrdinalIgnoreCase) == 0){
241 field = (FieldInfo)e.Value;
242 break;
246 if (field != null)
247 return new MemberInfo[]{field};
248 if (result == null) result = new MemberInfo[0];
249 return result;
252 public override MemberInfo[] GetMember(String name, BindingFlags bindingAttr){
253 return this.GetMember(name, bindingAttr, false);
256 private MemberInfo[] GetMember(String name, BindingFlags bindingAttr, bool wrapMembers){
257 MemberInfo[] members = this.GetLocalMember(name, bindingAttr, wrapMembers);
258 if (members.Length > 0) return members;
259 if (this.parent != null){
260 if (this.parent is JSObject){
261 members = ((JSObject)this.parent).GetMember(name, bindingAttr, true);
262 wrapMembers = false;
263 }else
264 members = this.parent.GetMember(name, bindingAttr);
265 foreach (MemberInfo mem in members){
266 if (mem.MemberType == MemberTypes.Field){
267 FieldInfo field = (FieldInfo)mem;
268 JSMemberField mfield = mem as JSMemberField;
269 if (mfield != null){ //This can only happen when running in the Evaluator
270 if (!mfield.IsStatic){
271 JSGlobalField gfield = new JSGlobalField(this, name, mfield.value, FieldAttributes.Public);
272 this.NameTable[name] = gfield;
273 this.field_table.Add(gfield);
274 field = mfield;
276 }else{
277 field = new JSPrototypeField(this.parent, (FieldInfo)mem);
278 if (!this.noExpando){
279 this.NameTable[name] = field;
280 this.field_table.Add(field);
283 return new MemberInfo[]{field};
285 if (!this.noExpando){
286 if (mem.MemberType == MemberTypes.Method){
287 FieldInfo field = new JSPrototypeField(this.parent,
288 new JSGlobalField(this, name,
289 LateBinding.GetMemberValue(this.parent, name, null, members),
290 FieldAttributes.Public|FieldAttributes.InitOnly));
291 this.NameTable[name] = field;
292 this.field_table.Add(field);
293 return new MemberInfo[]{field};
297 if (wrapMembers)
298 return ScriptObject.WrapMembers(members, this.parent);
299 else
300 return members;
302 return new MemberInfo[0];
305 public override MemberInfo[] GetMembers(BindingFlags bindingAttr){
306 MemberInfoList mems = new MemberInfoList();
307 SimpleHashtable uniqueMems = new SimpleHashtable(32);
309 if (!this.noExpando && this.field_table != null){ //Add any expando properties
310 IEnumerator enu = this.field_table.GetEnumerator();
311 while (enu.MoveNext()){
312 FieldInfo field = (FieldInfo)enu.Current;
313 mems.Add(field);
314 uniqueMems[field.Name] = field;
318 //Add the public members of the built-in objects if they don't already exist
319 if (this.isASubClass){
320 MemberInfo[] ilMembers = this.GetType().GetMembers(bindingAttr & ~BindingFlags.NonPublic); //Never expose non public members of old style objects
321 for (int i = 0, n = ilMembers.Length; i < n; i++){
322 MemberInfo ilMem = ilMembers[i];
324 //Hide any infrastructure stuff in JSObject
325 if (!ilMem.DeclaringType.IsAssignableFrom(Typeob.JSObject) && uniqueMems[ilMem.Name] == null){
326 MethodInfo method = ilMem as MethodInfo;
327 if (method == null || !method.IsSpecialName){
328 mems.Add(ilMem);
329 uniqueMems[ilMem.Name] = ilMem;
335 //Add parent members if they don't already exist
336 if (this.parent != null){
337 SimpleHashtable cache = this.parent.wrappedMemberCache;
338 if (cache == null)
339 cache = this.parent.wrappedMemberCache = new SimpleHashtable(8);
340 MemberInfo[] parentMems = ScriptObject.WrapMembers(((IReflect)this.parent).GetMembers(bindingAttr & ~BindingFlags.NonPublic), this.parent, cache);
341 for(int i = 0, n = parentMems.Length; i < n; i++){
342 MemberInfo parentMem = parentMems[i];
343 if(uniqueMems[parentMem.Name] == null){
344 mems.Add(parentMem);
345 //uniqueMems[parentMem.Name] = parentMem; //No need to add to lookup table - no one else will be looking.
350 return mems.ToArray();
353 internal override void GetPropertyEnumerator(ArrayList enums, ArrayList objects){
354 if (this.field_table == null) this.field_table = new ArrayList();
355 enums.Add(new ListEnumerator(this.field_table));
356 objects.Add(this);
357 if (this.parent != null)
358 this.parent.GetPropertyEnumerator(enums, objects);
361 internal override Object GetValueAtIndex(uint index){ //used by array functions
362 String name = System.Convert.ToString(index, CultureInfo.InvariantCulture);
363 //Do not defer to to routine below, since Array objects override it and could call back to this routine
364 FieldInfo field = (FieldInfo)(this.NameTable[name]);
365 if (field != null)
366 return field.GetValue(this);
367 else{
368 Object result = null;
369 if (this.parent != null)
370 result = this.parent.GetMemberValue(name);
371 else
372 result = Missing.Value;
373 if (this is StringObject && result == Missing.Value){
374 String str = ((StringObject)this).value;
375 if (index < str.Length)
376 return str[(int)index];
378 return result;
382 #if !DEBUG
383 [DebuggerStepThroughAttribute]
384 [DebuggerHiddenAttribute]
385 #endif
386 internal override Object GetMemberValue(String name){
387 FieldInfo field = (FieldInfo)this.NameTable[name];
388 if (field == null && this.isASubClass){
389 field = this.subClassIR.GetField(name, BindingFlags.Instance|BindingFlags.Static|BindingFlags.Public);
390 if (field != null){
391 if (field.DeclaringType == Typeob.ScriptObject) return Missing.Value;
392 }else{
393 PropertyInfo prop = this.subClassIR.GetProperty(name, BindingFlags.Instance|BindingFlags.Public);
394 if (prop != null && !prop.DeclaringType.IsAssignableFrom(Typeob.JSObject))
395 return JSProperty.GetGetMethod(prop, false).Invoke(this, BindingFlags.SuppressChangeType, null, null, null);
396 try{
397 MethodInfo method = this.subClassIR.GetMethod(name, BindingFlags.Public|BindingFlags.Static);
398 if (method != null){
399 Type dt = method.DeclaringType;
400 if (dt != Typeob.JSObject && dt != Typeob.ScriptObject && dt != Typeob.Object)
401 return new BuiltinFunction(this, method);
403 }catch(AmbiguousMatchException){}
406 if (field != null)
407 return field.GetValue(this);
408 if (this.parent != null)
409 return this.parent.GetMemberValue(name);
410 return Missing.Value;
413 internal SimpleHashtable NameTable{
414 get{
415 SimpleHashtable result = this.name_table;
416 if (result == null){
417 this.name_table = result = new SimpleHashtable(16);
418 this.field_table = new ArrayList();
420 return result;
424 void IExpando.RemoveMember(MemberInfo m){
425 this.DeleteMember(m.Name);
428 #if !DEBUG
429 [DebuggerStepThroughAttribute]
430 [DebuggerHiddenAttribute]
431 #endif
432 internal override void SetMemberValue(String name, Object value){
433 this.SetMemberValue2(name, value);
436 public void SetMemberValue2(String name, Object value){
437 FieldInfo field = (FieldInfo)this.NameTable[name];
438 if (field == null && this.isASubClass)
439 field = this.GetType().GetField(name);
440 if (field == null){
441 if (this.noExpando)
442 return;
443 field = new JSExpandoField(name);
444 this.name_table[name] = field;
445 this.field_table.Add(field);
447 if (!field.IsInitOnly && !field.IsLiteral)
448 field.SetValue(this, value);
451 internal override void SetValueAtIndex(uint index, Object value){
452 this.SetMemberValue(System.Convert.ToString(index, CultureInfo.InvariantCulture), value);
455 internal virtual void SwapValues(uint left, uint right){
456 String left_name = System.Convert.ToString(left, CultureInfo.InvariantCulture);
457 String right_name = System.Convert.ToString(right, CultureInfo.InvariantCulture);
458 FieldInfo left_field = (FieldInfo)(this.NameTable[left_name]);
459 FieldInfo right_field = (FieldInfo)(this.name_table[right_name]);
460 if (left_field == null)
461 if (right_field == null)
462 return;
463 else{
464 this.name_table[left_name] = right_field;
465 this.name_table.Remove(right_name);
467 else if (right_field == null){
468 this.name_table[right_name] = left_field;
469 this.name_table.Remove(left_name);
470 }else{
471 this.name_table[left_name] = right_field;
472 this.name_table[right_name] = left_field;
476 public override String ToString(){
477 return Convert.ToString(this);